package asteroids.model;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;

import org.antlr.v4.runtime.RecognitionException;

import asteroids.CollisionListener;
import asteroids.ModelException;
import asteroids.model.programs.Program;
import asteroids.model.programs.ProgramFactoryImpl;
import asteroids.model.programs.Type;
import asteroids.model.programs.Expression.Expression;
import asteroids.model.programs.Expression.SelfLiteral;
import asteroids.model.programs.Statement.Statement;
import asteroids.model.programs.parsing.ProgramParser;

/**
 * @version 0.3
 * @author Tandan Sunil, Van Deun Luca
 */
public class Facade implements
		asteroids.IFacade<World, Ship, Asteroid, Bullet, Program> {

	@Override
	public World createWorld(double width, double height) {
		try {
			return new World(height, width);
		} catch (Exception e) {
			throw new ModelException(e.getMessage());
		}

	}

	@Override
	public double getWorldWidth(World world) {
		if (!World.isValidWorld(world)) {
			throw new ModelException("Invalid World");
		}
		return world.getWidth();
	}

	@Override
	public double getWorldHeight(World world) {
		if (!World.isValidWorld(world)) {
			throw new ModelException("Invalid World");
		}
		return world.getHeight();
	}

	@Override
	public Set<Ship> getShips(World world) {
		if (!World.isValidWorld(world)) {
			throw new ModelException("Invalid World");
		}
		return world.getShips();
	}

	@Override
	public Set<Asteroid> getAsteroids(World world) {
		if (!World.isValidWorld(world)) {
			throw new ModelException("Invalid World");
		}
		return world.getAsteroids();
	}

	@Override
	public Set<Bullet> getBullets(World world) {
		if (!World.isValidWorld(world)) {
			throw new ModelException("Invalid World");
		}
		return world.getBullets();
	}

	@Override
	public void addShip(World world, Ship ship) {
		if (!World.isValidWorld(world))
			throw new ModelException("Invalid World");

		if (!Ship.isValidEntity(ship))
			throw new ModelException("Invalid Ship");
		world.addAsEntity(ship);

	}

	@Override
	public void addAsteroid(World world, Asteroid asteroid) {
		if (!World.isValidWorld(world))
			throw new ModelException("Invalid World");

		if (!Entity.isValidEntity(asteroid))
			throw new ModelException("Invalid asteroid");
		world.addAsEntity(asteroid);

	}

	@Override
	public void removeShip(World world, Ship ship) {
		if (!World.isValidWorld(world))
			throw new ModelException("Invalid World");

		if (!Entity.isValidEntity(ship))
			throw new ModelException("Invalid Ship");
		world.removeAsEntity(ship);

	}

	@Override
	public void removeAsteroid(World world, Asteroid asteroid) {
		if (!World.isValidWorld(world))
			throw new ModelException("Invalid World");

		if (!Entity.isValidEntity(asteroid))
			throw new ModelException("Invalid asteroid");
		world.removeAsEntity(asteroid);

	}

	@Override
	public void evolve(World world, double dt,
			CollisionListener collisionListener) {
		if (!World.isValidWorld(world))
			throw new ModelException("Invalid World");
		world.evolve(dt, collisionListener);

	}

	@Override
	public Ship createShip(double x, double y, double xVelocity,
			double yVelocity, double radius, double direction, double mass)
			throws ModelException {
		try {
			return new Ship(x, y, xVelocity, yVelocity, radius, direction, mass);
		} catch (Exception e) {
			throw new ModelException(e.getMessage());
		}
	}

	@Override
	public boolean isShip(Object o) {
		return (o instanceof Ship) ? true : false;
	}

	@Override
	public double getShipX(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");

		return ship.getXCoordinate();
	}

	@Override
	public double getShipY(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");

		return ship.getYCoordinate();
	}

	@Override
	public double getShipXVelocity(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");

		return ship.getXVelocity();
	}

	@Override
	public double getShipYVelocity(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		return ship.getYVelocity();
	}

	@Override
	public double getShipRadius(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");

		return ship.getRadius();
	}

	@Override
	public double getShipDirection(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		return ship.getAngle();
	}

	@Override
	public double getShipMass(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		return ship.getMass();
	}

	@Override
	public World getShipWorld(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		return ship.getWorld();
	}

	@Override
	public boolean isShipThrusterActive(Ship ship) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		return ship.isShipThrusterActive();
	}

	@Override
	public void setThrusterActive(Ship ship, boolean active) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		ship.setThruster(active);

	}

	@Override
	public void turn(Ship ship, double angle) {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		ship.addAngle(angle);

	}

	@Override
	public void fireBullet(Ship ship) throws ModelException {
		if (ship == null)
			throw new ModelException("Invalid Ship, NullPointer");
		try {
			ship.fire();
		} catch (Exception e) {
			throw new ModelException(e.getMessage());
		}

	}

	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity,
			double yVelocity, double radius) throws ModelException {
		try {
			return new Asteroid(x, y, xVelocity, yVelocity, radius);
		} catch (Exception e) {
			throw new ModelException(e.getMessage());
		}

	}

	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity,
			double yVelocity, double radius, Random random)
			throws ModelException {
		try {
			return new Asteroid(x, y, xVelocity, yVelocity, radius);

		} catch (Exception e) {
			throw new ModelException(e.getMessage());
		}
	}

	@Override
	public boolean isAsteroid(Object o) {

		return (o instanceof Asteroid) ? true : false;
	}

	@Override
	public double getAsteroidX(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getXCoordinate();
	}

	@Override
	public double getAsteroidY(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getYCoordinate();
	}

	@Override
	public double getAsteroidXVelocity(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getXVelocity();
	}

	@Override
	public double getAsteroidYVelocity(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getYVelocity();
	}

	@Override
	public double getAsteroidRadius(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getRadius();
	}

	@Override
	public double getAsteroidMass(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getMass();
	}

	@Override
	public World getAsteroidWorld(Asteroid asteroid) {
		if (asteroid == null)
			throw new ModelException("Invalid Asteroid, NullPointer");
		return asteroid.getWorld();
	}

	@Override
	public boolean isBullets(Object o) {
		return (o instanceof Bullet) ? true : false;
	}

	@Override
	public double getBulletX(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getXCoordinate();
	}

	@Override
	public double getBulletY(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getYCoordinate();
	}

	@Override
	public double getBulletXVelocity(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getXVelocity();
	}

	@Override
	public double getBulletYVelocity(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getYVelocity();
	}

	@Override
	public double getBulletRadius(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getRadius();
	}

	@Override
	public double getBulletMass(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getMass();
	}

	@Override
	public World getBulletWorld(Bullet bullet) {
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getWorld();
	}

	@Override
	public Ship getBulletSource(Bullet bullet) {
		try{
		if (bullet == null)
			throw new ModelException("Invalid Bullet, NullPointer");
		return bullet.getSource();
		}catch(Exception e){throw new ModelException(e.getMessage());}
		
	}

	@Override
	public asteroids.IFacade.ParseOutcome<Program> parseProgram(String text) {
		try{
		ProgramFactoryImpl factory = new ProgramFactoryImpl();
		ProgramParser<Expression, Statement, Type> parser = new ProgramParser<>(
				factory);
		try {
			parser.parse(text);
			List<String> errors = parser.getErrors();
			if (!errors.isEmpty()) {
				return ParseOutcome.failure(errors.get(0));
			} else {
				return ParseOutcome.success(new Program(parser.getGlobals(),
						parser.getStatement(),factory.getVariables(),(SelfLiteral) factory.createSelf(0, 0)));
			}
		} catch (RecognitionException e) {
			return ParseOutcome.failure(e.getMessage());
		}

		}catch(Exception e){throw new ModelException(e.getMessage());}
		
	}

	@Override
	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromStream(
			InputStream stream) throws IOException {
		try{
		Scanner sc = new java.util.Scanner(stream);
		String content = "";
		while (sc.hasNextLine()) {
			content = content + sc.nextLine() + "\n";
		}
		sc.close();
//		System.out.println(content);
		return parseProgram(content);
		}catch(Exception e){throw new ModelException(e.getMessage());}
		
		
	}

	@Override
	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromUrl(URL url)
			throws IOException {
		try{
			return loadProgramFromStream(url.openStream());
		}catch(Exception e){throw new ModelException(e.getMessage());}
		
	}

	@Override
	public boolean isTypeCheckingSupported() {
		return false;
	}

	@Override
	public asteroids.IFacade.TypeCheckOutcome typeCheckProgram(Program program) {
		return null;
	}

	@Override
	public void setShipProgram(Ship ship, Program program) {
		try{ship.setProgram(program);
		}catch(Exception e){throw new ModelException(e.getMessage());}
		

	}

}
